﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using jabber.client;
using JaUtils.Properties;
using System.Threading;
using jabber.protocol.client;
using System.ServiceModel.Syndication;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Reflection;

namespace JaUtils
{
	class FeedBot
	{
		static ManualResetEvent connected = new ManualResetEvent(false);
		static Exception startUpError = null;
		static JabberClient client;
		static FeedBotDBDataContext db;

		static int Main(string[] args)
		{
			using (client = new JabberClient())
			using (db = new FeedBotDBDataContext(Settings.Default.FeedDB))
			{
				if (!db.DatabaseExists())
				{
					//db.DeleteDatabase();
					if (Settings.Default.CreateDB) db.CreateDatabase();
					else
					{
						Console.Error.WriteLine("database does not exists");
						return -1;
					}
				}

				client.User = Settings.Default.UserName;
				client.Password = Settings.Default.Password;
				client.Server = Settings.Default.JabberServer;

				client.AutoRoster = true;
				client.AutoReconnect = 30;
				client.AutoIQErrors = true;
				client.AutoLogin = true;
				client.AutoStartCompression = true;
				client.AutoStartTLS = true;

				client.OnError += new bedrock.ExceptionHandler(client_OnError);

				client.OnAuthenticate += new bedrock.ObjectHandler(client_OnAuthenticate);
				client.OnMessage += new MessageHandler(client_OnMessage);

				Console.WriteLine("connecting...");
				client.Connect();

				connected.WaitOne();

				client.Presence(PresenceType.available, "running main loop", "running main loop", 1);

				if (startUpError != null)
					return -1;

				Console.WriteLine("running main loop...");

				while (true)
				{
					var now = DateTime.Now;
					var feeds = from feed in db.UserFeeds
								where feed.LastCheck.AddSeconds(feed.CheckDelay) < now
								select feed;

					foreach (var feedInfo in feeds)
					{
						SyndicationFeed feed;
						try
						{
							feed = RssReader.Read(
								new Uri(feedInfo.FeedUri),
								feedInfo.FeedUserName,
								feedInfo.FeedPassword,
								RssReader.FeedType(feedInfo.FeedType));
						} catch
						{
							continue;
						}

						var lastCheck = feedInfo.LastCheck;

						var show =
							from item in feed.Items
							where item.PublishDate.DateTime > lastCheck
							orderby item.PublishDate.DateTime ascending
							select item;

						foreach (var message in show)
						{
							client.Message(feedInfo.User, FormatFeedMessage(message));
							if (message.PublishDate.DateTime > feedInfo.LastCheck)
								feedInfo.LastCheck = message.PublishDate.DateTime;
						}
						lock (db)
						{
							db.SubmitChanges();
						}
					}

					Thread.Sleep(30000);
				}
			}
		}

		static void client_OnMessage(object sender, Message msg)
		{
			var user = msg.From.Bare;

			if (msg.Body.Length == 0)
			{
				client.Message(msg.From.ToString(), "only nonempty messages are accepted\ntype \"?\" to get help");
				return;
			}

			try{
				var answer = ReplyTo(user, msg.Body);
				client.Message(msg.From.ToString(), answer);
			}catch(Exception error)
			{
				client.Message(msg.From.ToString(), 
					string.Format("{0}\n{1}",
					error.GetType(),
					error.Message));
			}
		}

		private static string ReplyTo(string user, string query)
		{
			var result = new StringBuilder();
			var lines = new Queue<string>(query.Split('\n'));

			while (lines.Count > 0)
			{
				var line = lines.Dequeue();

				if (string.IsNullOrEmpty(line)) continue;

				switch (line[0])
				{
				case '?':
					if (line.Trim() == "? +")
						result.Append(HelpOnAdd);
					else if (line.Trim() == "? -")
						result.Append(HelpOnRemove);
					else if (line.Trim() == "? v")
						result.Append(HelpOnView);
					else if (line.Trim() == "? =")
						result.AppendLine(HelpOnList);
					else
						result.Append(HelpOnApp);
					break;
				case '+':
					var uri = line.Substring(1).Trim().ToLower();
					var feedinfo = (from feed in db.UserFeeds
								   where (feed.User == user) && (feed.FeedUri == uri)
								   select feed).FirstOrDefault();

					if (feedinfo == null)
					{
						feedinfo = new UserFeed { FeedUri = uri, User = user };
						feedinfo.CheckDelay = 900;
						feedinfo.FeedType = "xhtml";
						feedinfo.LastCheck = new DateTime(1990, 1, 1);
						db.UserFeeds.InsertOnSubmit(feedinfo);
					}

					do
					{
						if (lines.Count == 0)
							break;

						var param = lines.Peek();
						if (param.StartsWith("lastCheck: "))
							feedinfo.LastCheck = DateTime.Parse(param.Substring("lastCheck: ".Length));
						else if (param.StartsWith("login: "))
							feedinfo.FeedUserName = param.Substring("login: ".Length);
						else if (param.StartsWith("password: "))
							feedinfo.FeedPassword = param.Substring("password: ".Length);
						else if (param.StartsWith("delay: "))
							feedinfo.CheckDelay = int.Parse(param.Substring("delay: ".Length));
						else if (param.StartsWith("type: "))
						{
							var typeStr = param.Substring("type: ".Length);
							RssReader.FeedType(typeStr);
							feedinfo.FeedType = typeStr;
						} else break;

						lines.Dequeue();
					} while (true);
					break;

				case '-':
					uri = line.Substring(1).Trim().ToLower();
					feedinfo = (from feed in db.UserFeeds
									where (feed.User == user) && (feed.FeedUri == uri)
									select feed).FirstOrDefault();

					if (feedinfo == null)
					{
						result.AppendLine("feed " + uri + " does not exists");
					} else
					{
						db.UserFeeds.DeleteOnSubmit(feedinfo);
					}
					break;
				case 'v':
				case '=':
					result.AppendLine(line[0] + " is not implemented yet");
					break;
				default:
					result.AppendLine("type \"?\" to get help");
					break;
				}

				result.AppendLine();
			}

			lock (db) db.SubmitChanges();

			return result.ToString();
		}

		static string HelpOnApp
		{
			get
			{
				var result = new StringBuilder();
				result.AppendLine("Lost JaUtils.FeedBot v" + Assembly.GetExecutingAssembly().GetName().Version.ToString());
				result.AppendLine();
				result.AppendLine("usage:");
				result.AppendLine("operator param");
				result.AppendLine("paramname: value");
				result.AppendLine("paramname: value");
				result.AppendLine("...");
				result.AppendLine();
				result.AppendLine("available operators:");
				result.AppendLine("+ - add new feed subscribtion");
				result.AppendLine("- - remove existing subscription");
				result.AppendLine("= - list subscriptions");
				result.AppendLine("v - view feed");
				result.AppendLine("? - get help (example: ? +)");
				return result.ToString();
			}
		}

		static string HelpOnAdd
		{
			get
			{
				var result = new StringBuilder();
				result.AppendLine("usage of + operator:");
				result.AppendLine("+feed_uri");
				result.AppendLine();
				result.AppendLine("parameters");
				result.AppendLine("lastCheck - excplicitly sets last check date and time to prevent reading older messages (default is 1.1.1990)");
				result.AppendLine("login - specifies login to access the feed");
				result.AppendLine("password - specifies password to access the feed (ignored when no login is provided)");
				result.AppendLine("delay - specifies period between feed scans in seconds (default value is 900=15min)");
				result.AppendLine("type - plaintext/html/xhtml - threats feed contents as (default value is xhtml)");
				return result.ToString();
			}
		}

		static string HelpOnRemove
		{
			get
			{
				var result = new StringBuilder();
				result.AppendLine("usage of - operator:");
				result.AppendLine("-feed_uri");
				return result.ToString();
			}
		}

		static string HelpOnList
		{
			get
			{
				var result = new StringBuilder();
				result.AppendLine("usage of = operator:");
				result.AppendLine("=feed_uri");
				return result.ToString();
			}
		}

		static string HelpOnView
		{
			get
			{
				var result = new StringBuilder();
				result.AppendLine("usage of v operator:");
				result.AppendLine("vfeed_uri");
				result.AppendLine();
				result.AppendLine("parameters");
				result.AppendLine("since - excplicitly sets date and time to prevent reading older messages (default is 1.1.1990)");
				result.AppendLine("login - specifies login to access the feed");
				result.AppendLine("password - specifies password to access the feed (ignored when no login is provided)");
				result.AppendLine("type - plaintext/html/xhtml - threats feed contents as (default value is xhtml)");
				return result.ToString();
			}
		}

		static string FormatFeedMessage(SyndicationItem message)
		{
			var result = new StringBuilder();

			if (message.Title != null) result.AppendLine((message.Title as TextSyndicationContent).Text);

			foreach (var author in message.Authors)
			{
				result.Append(author.Email);
				result.Append("; ");
			}
			result.AppendLine();
			result.AppendLine();

			if (message.Content != null)
			{
				var text = message.Content as TextSyndicationContent;
				switch (text.Type)
				{
				case "xhtml":
				case "html":
					var output = text.Text.Replace("&lt;p&gt;", "\n  ");
					output = output
						.Replace("&amp;", "&")
						.Replace("&lt;", "<")
						.Replace("&gt;", ">")
						.Replace("&quot;", "\"")
						.Replace("&nbsp;", " ");
					output = Regex.Replace(output, "<[^>]*>", "");
					result.AppendLine(output);
					break;
				default:
					result.AppendLine(text.Text);
					break;
				}
				result.AppendLine();
			}

			result.AppendLine(message.PublishDate.ToString());
			foreach (var link in message.Links)
				result.AppendLine(link.Uri.ToString());

			return result.ToString();
		}

		static void client_OnAuthenticate(object sender)
		{
			connected.Set();
		}

		static void client_OnError(object sender, Exception ex)
		{
			Console.Error.WriteLine("[{0}] {1}: {2}", DateTime.Now, ex.GetType(), ex.Message);
			if (ex is SocketException)
			{
				startUpError = ex;
				connected.Set();
			}
		}
	}
}
